package com.nbilyk.ntween {
	import flash.display.Shape;
	import flash.events.Event;
	import flash.utils.getTimer;

	/**
	 * NTimeEngine is a class to update ITimeUpdatable objects with the
	 * time elapsed after their registration.  
	 */
	public class NTimeEngine {

		private static var _instance:NTimeEngine;

		/**
		 * The timeScale is used to speed up or slow down the elapsed time sent to the 
		 * setCurrentTime function on the ITimeUpdatable objects. 
		 * e.g. a timeScale of 2 will be twice as fast.
		 */
		public static var timeScale:Number = 1;

		/**
		 * A Vector of the ITimeUpdatable objects registered.
		 * @see NTimeEngine#registerObject
		 */
		public var registeredObjects:Vector.<ITimeUpdatable> = new Vector.<ITimeUpdatable>();
		
		/**
		 * A Vector of the times each ITimeUpdatable object was registered.
		 * This Vector is parallel to the registeredObjects Vector.
		 */
		private var registeredTimes:Vector.<int> = new Vector.<int>();
		
		private var _paused:Boolean = true;
		private var lastUpdateTime:int;
		
		/**
		 * Used simply to have an enter frame handler.
		 */
		private var timerShape:Shape;

		/**
		 * The time value the time engine was paused.
		 */
		private var pausedTime:int;
		
		/**
		 * The total pause time in milliseconds.
		 */
		private var totalPausedTime:int;

		public function NTimeEngine(s:SingletonEnforcer) {
			timerShape = new Shape();
			lastUpdateTime = getTimer();
			paused = false;
		}

		protected function refreshEnterFrameRequired():void {
			if (paused || registeredObjects.length == 0) {
				if (timerShape.hasEventListener(Event.ENTER_FRAME)) {
					timerShape.removeEventListener(Event.ENTER_FRAME, enterFrameHandler);
				}
			} else {
				if (!timerShape.hasEventListener(Event.ENTER_FRAME)) {
					timerShape.addEventListener(Event.ENTER_FRAME, enterFrameHandler);
				}
			}
		}

		/**
		 * Pauses the time engine.
		 */
		public function get paused():Boolean {
			return _paused;
		}

		public function set paused(value:Boolean):void {
			if (_paused == value)
				return;
			_paused = value;
			if (value) {
				pausedTime = getTimer();
			} else {
				if (pausedTime)
					totalPausedTime += getTimer() - pausedTime;
			}
			refreshEnterFrameRequired();
		}

		/**
		 * @see #paused
		 */
		public function pause():void {
			paused = true;
		}

		/**
		 * Gets the NTimeEngine singleton.
		 */
		public static function get instance():NTimeEngine {
			if (!_instance)
				_instance = new NTimeEngine(new SingletonEnforcer());
			return _instance;
		}

		private function enterFrameHandler(event:Event):void {
			var currentTimer:int = getTimer();
			lastUpdateTime += int((currentTimer - lastUpdateTime - totalPausedTime) * timeScale);
			totalPausedTime = 0;

			var i:uint = 0;
			for each (var registeredObj:ITimeUpdatable in registeredObjects) {
				var registeredTime:int = int(registeredTimes[i]);
				registeredObj.setCurrentTime(lastUpdateTime - registeredTime);
				i++;
			}
		}
		
		/**
		 * Registers an ITimeUpdatable object with the time engine.
		 */
		public function registerObject(obj:ITimeUpdatable):void {
			registeredObjects.push(obj);
			if (paused)
				registeredTimes.push(pausedTime);
			else
				registeredTimes.push(getTimer());
			refreshEnterFrameRequired();
		}

		/**
		 * Unregisters an ITimeUpdatable object from the time engine. 
		 * @return Returns true if the tween was removed, returns false if not found.
		 */
		public function unregisterObject(obj:ITimeUpdatable):Boolean {
			var index:int = registeredObjects.indexOf(obj);
			if (index == -1) return false;
			registeredObjects.splice(index, 1);
			registeredTimes.splice(index, 1);
			refreshEnterFrameRequired();
			return true;
		}

		/**
		 * Returns true if the ITimeUpdatable object is registered with the time engine.
		 */
		public function isObjectRegistered(obj:ITimeUpdatable):Boolean {
			var index:int = registeredObjects.indexOf(obj);
			return (index != -1);
		}
	}
}

class SingletonEnforcer {
}